package com.jx.ui;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

public class Crack {
    // 窗口宽
    public static final int DEFAULT_WIDTH = 500;
    // 窗口高
    public static final int DEFAULT_HEIGHT = 400;
    // 主面板
    private JPanel mainPanel;
    //输出控制台
    private JTextArea console;
    //文件选择器
    private JFileChooser chooser;
    //所有jar文件
    public static ArrayList<File> jar_path = new ArrayList<>();
    //软件名称
    public static String soft_name = null;
    //软件版本
    public static String version = null;
    //存在验证类的jar文件
    public static String jar = null;
    //证书验证类
    public static String licence = null;
    //输出目录
    public static String outdir = null;
    //可支持的破解软件
    public static final ArrayList<String> soft = new ArrayList<String>() {
        private static final long serialVersionUID = 1233997685166666232L;

        {
            add("jxbrowser");
            add("jexplorer");
            add("comfyj");
            add("jexcel");
        }
    };
    //可支持的破解软件对应软件名
    public static final HashMap<String, String> names = new HashMap<String, String>() {
        private static final long serialVersionUID = -5597880628803924629L;

        {
            put("jxbrowser", "JxBrowser");
            put("jexplorer", "JExplorer");
            put("comfyj", "ComfyJ");
            put("jexcel", "JExcel");
        }
    };

    public static void main(String[] args) {
        Crack crack = new Crack();

        JFrame frame = new JFrame("licence助手");
        frame.setContentPane(crack.mainPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        crack.iniPanel(frame);

        frame.pack();
        frame.setVisible(true);
        //设置窗体大小需要放最下面才生效
        frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }

    public void iniPanel(JFrame frame) {
        // 设置工具栏
        JMenuBar menuBar = new JMenuBar();
        frame.setJMenuBar(menuBar);

        // 设置菜单
        JMenu menu = new JMenu("文件");
        menuBar.add(menu);

        JMenuItem openItem = new JMenuItem("打开");
        menu.add(openItem);
        openItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                openChooseDialog(frame);
            }
        });

        JMenuItem exitiItem = new JMenuItem("退出");
        menu.add(exitiItem);
        exitiItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });

        JMenuItem aboutItem = new JMenuItem("关于");
        menu.add(aboutItem);
        aboutItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frame, "https://googlewellblog.wordpress.com/", "关于助手",
                        JOptionPane.INFORMATION_MESSAGE);
            }
        });

        // 创建文件选择器
        chooser = new JFileChooser();
        // 文件选择过滤文件类型
        FileNameExtensionFilter filter = new FileNameExtensionFilter("jar文件", "jar");
        // 设置文件选择器的过滤文件类型
        chooser.setFileFilter(filter);
        addConsoleInfo("请点击文件菜单选择打开,可多选文件,为防止查找破解类失败,可选择所有相关jar文件.");
    }

    /**
     * 打开文件选择对话框
     */
    private void openChooseDialog(JFrame frame) {
        chooser.setMultiSelectionEnabled(true);
        chooser.setCurrentDirectory(new File("."));
        // 打开文件选择窗口
        int result = chooser.showOpenDialog(frame);
        // 如果选择文件
        if (result == JFileChooser.APPROVE_OPTION) {
            //清空缓存
            clearCache();
            File[] files = chooser.getSelectedFiles();
            if (files.length > 0) {
                // 破解
                crack(frame, files);
            } else {
                addConsoleInfo("选择文件数为0!");
            }
        } else {
            addConsoleInfo("未选择文件!");
        }
    }

    private void crack(JFrame frame, File[] files) {
        outdir = files[0].getAbsolutePath().replace(files[0].getName(), "");
        //装载依赖类
        jar_path.addAll(Arrays.asList(files));
        // 类加载器，用于从jar文件加载类
        URLClassLoader myClassLoader = Tools.getClassLoaderFromJar(jar_path);
        if (myClassLoader == null) {
            addConsoleInfo("创建类加载器失败.");
            return;
        }
        // 识别软件名称和版本
        String file = "";
        for (int i = 0; i < files.length; i++) {
            addConsoleInfo("路径:" + files[i].getAbsolutePath());
            // 获取文件名，一般类如comfyj-2.13，名称-版本号形式
            file = files[i].getName();
            String name = file.substring(0, file.lastIndexOf('.'));
            String[] info = name.split("-");
            //有的命名不是名称-版本号形式，可能为名称-平台-版本号形式,后面平台和版本号顺序可能不统一
            if (info.length > 1) {
                if (soft.contains(info[0])) {
                    soft_name = info[0];
                    if (info[1].matches("\\d+\\.\\d+(\\.\\d+)?")) {
                        version = info[1];
                        addConsoleInfo("软件名称:" + soft_name + "  版本:" + version);
                    } else if (info.length > 2 && info[2].matches("\\d+\\.\\d+(\\.\\d+)?")) {
                        version = info[2];
                        addConsoleInfo("软件名称:" + soft_name + "  版本:" + version);
                    }
                }
            }
            if (soft_name == null || version == null) {
                addConsoleInfo("识别软件名称和版本失败,请手动修改为名称-版本号形式,如jxbrowser-6.24.jar");
                return;
            }
            // 查找证书验证类
            try {
                String tmp = Tools.getVertifyLicenceClass(myClassLoader, soft_name, files[i]);
                if (tmp != null) {
                    licence = tmp;
                    jar = files[i].getAbsolutePath();
                    addConsoleInfo("验证类为:" + licence);
                } else {
                    addConsoleInfo("未发现验证类.");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (licence == null) {
            addConsoleInfo("所有包未发现验证类,查找验证类失败.");
            return;
        }
        // 加载数据
        try {
            InputStream is = myClassLoader.getResourceAsStream(licence);
            byte[] data = Tools.patchData(is);
            // System.out.println(DatatypeConverter.printHexBinary(data));
            // DatatypeConverter.parseHexBinary(data);
            is.close();
            myClassLoader.close();

            if (data == null) {
                addConsoleInfo("验证类中没有查找需要修改的属性，请确认是否已经破解.");
                return;
            }

            //创建整合包
            boolean iscreate = createJar(soft_name, data);

            //创建失败则不创建整合包
            if (!iscreate) {
                //破解类类名
                String name = licence.substring(licence.lastIndexOf('/') + 1);
                FileOutputStream fos = new FileOutputStream(outdir + name);
                fos.write(data);
                fos.close();

                addConsoleInfo("创建破解类成功，请将目录下生成的" + name + "覆盖jar文件(" + jar + ")如下路径文件" + licence);

                //创建证书
                createLicence(soft_name, version);
                addConsoleInfo("目录下已生成证书license.jar，请将它添加你的lib中，或者将license.jar的META-INF/teamdev.licenses文件放入jxbroswer的jar文件，可省略license.jar。");
            }
            addConsoleInfo("如果有安全验证问题，请删除对应jxbrowser的jar文件内META-INF\\TEAMDEV-.RSA和META-INF\\TEAMDEV-.SF(或类似后缀名)文件。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出信息到控制台
     *
     * @param info
     */
    public void addConsoleInfo(String info) {
        console.append(info + "\n");
    }


    /**
     * 创建证书文件
     *
     * @param name
     * @param version
     * @throws IOException
     */
    public void createLicence(String name, String version) throws IOException {
        String model = "Product: %s\r\n" + "Version: %s.x\r\n" + "Licensed to: googlewell\r\n"
                + "License type: Enterprise\r\n" + "License info: %s License\r\n" + "Expiration date: NEVER\r\n"
                + "Support expiration date: NO SUPPORT\r\n" + "Generation date: 04-07-2018\r\n"
                + "Platforms: win32/x86;win32/x64;mac/x86;mac/x64;linux/x86;linux/x64\r\n"
                + "Company name: TeamDev Ltd.\r\n" + "SigB: 1\r\n" + "SigA: 1";
        version = version.substring(0, version.indexOf('.'));
        byte[] licence = String.format(model, names.get(name), version, names.get(name)).getBytes();
        JarOutputStream jos = new JarOutputStream(new FileOutputStream(outdir + "license.jar"));
        jos.putNextEntry(new JarEntry("META-INF/teamdev.licenses"));
        jos.write(licence, 0, licence.length);
        jos.close();
    }

    /**
     * 读取jar配置文件中的版本
     *
     * @param file
     * @return
     */
    public String getJarVersion(File file) {
        try {
            JarFile jfile = new JarFile(file);
            Attributes attr = jfile.getManifest().getMainAttributes();
            return attr.getValue("Bundle-Version");
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public boolean createJar(String name, byte[] data) {
        String crack = jar.replace(".jar", "-crack.jar");
        boolean isSucess = false;
        try {
            //原文件
            JarFile jfile = new JarFile(jar);
            //破解文件
            JarOutputStream jos = new JarOutputStream(new FileOutputStream(crack));
            //遍历原文件，写入破解文件
            Enumeration<JarEntry> entries = jfile.entries();

            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();

                //java.util.zip.ZipException: invalid entry size
                //解决上述错误需要创建新的对象和流
                ZipEntry zipEntry = new ZipEntry(entry.getName());
                InputStream zis = jfile.getInputStream(entry);

                jos.putNextEntry(zipEntry);

                if (entry.getName().equals(licence)) {//破解类写入破解数据
                    jos.write(data);
                } else {
                    isSucess = inToOut(zis, jos);
                    if (!isSucess) {
                        return false;
                    }
                }
            }
            //添加证书文件
            isSucess = addLicence(jos, name);
            jos.close();
            if (!isSucess) {
                return isSucess;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        addConsoleInfo("创建破解包成功，文件为:" + crack);
        return true;
    }

    public static boolean inToOut(InputStream jis, OutputStream jos) {
        try {
            byte[] data = new byte[1024];
            //加上n，防止最后读取的不到1024，最后还是写入1024个数据，有脏数据
            //不要用available()，available方法返回估计的当前流可用长度，不是流的总长度，而且是估计值
            int n=0;
            while ((n=jis.read(data))!=-1){
                jos.write(data,0,n);
            }
            jis.close();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public static boolean addLicence(JarOutputStream jos, String name) {
        String model = "Product: %s\r\n" + "Version: %s.x\r\n" + "Licensed to: googlewell\r\n"
                + "License type: Enterprise\r\n" + "License info: %s License\r\n" + "Expiration date: NEVER\r\n"
                + "Support expiration date: NO SUPPORT\r\n" + "Generation date: 04-07-2018\r\n"
                + "Platforms: win32/x86;win32/x64;mac/x86;mac/x64;linux/x86;linux/x64\r\n"
                + "Company name: TeamDev Ltd.\r\n" + "SigB: 1\r\n" + "SigA: 1";
        version = version.substring(0, version.indexOf('.'));
        byte[] licence = String.format(model, names.get(name), version, names.get(name)).getBytes();
        try {
            jos.putNextEntry(new JarEntry("META-INF/teamdev.licenses"));
            jos.write(licence, 0, licence.length);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 清空缓存数据
     */
    public static void clearCache() {
        licence = null;
        soft_name = null;
        version = null;
        jar_path.clear();
    }
}
